//==============================================================================
//
//             DLexer - the lexer class in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-22 13:28:56 $ $Revision: 1.21 $
//
//==============================================================================

#include "ofc/DLexer.h"

#include <ctype.h>
#include <string.h>


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DSource.h"
#include "ofc/DRegEx.h"

#ifdef HAVE_DREGEX
#define HAVE_DLEXER     (1)
#endif

#include "ofc/DText.h"
#include "ofc/DList.h"

#include "ofc/DTextReadable.h"


#ifdef HAVE_DLEXER
  
// The DLexer class implements a (simple) lexical scanner. The scanner
// scans a text stream for constant strings and regular expressions.
// There are two sets of services nextString+nextExpression and 
// checkString+checkExpression. The first set check for the string or
// expression and if it is a match, the scanner location is moved to
// the next location in the source. The second set of services only 
// return the result of the match, the client must call the service
// 'next' to move the scanner location.

@interface DLexer : Object
{
@private
  DList            *_sources;    // the stack with the sources
  DSource          *_source;     // the source for the scanner
   
  DText            *_text;       // the last scanned text
  BOOL              _eoff;       // is end of file reached ?
  unsigned          _scanned;    // the length of the scanned text

  DRegEx           *_whiteSpace; // the white space expression
  DRegEx           *_expression; // the normally used regular expression

  BOOL              _caseSensitive; // is the lexer case sensitive (def: YES)
}

#endif

#endif



#ifdef HAVE_DLEXER


@implementation DLexer


//// Constructors

//
// Initialise a lexer
//
// @return the object
//

- (DLexer *) init
{
  [super init];

  _caseSensitive = YES;
  
  _sources       = [DList new];
  _source        = nil;
   
  _text          = [DText new];
  _eoff          = NO;
  _scanned       = 0;

  _whiteSpace    = [DRegEx new];
  _expression    = [DRegEx new];
  
  [self whiteSpace :"[[:space:]]*"];
  
  return self;
}

//
// Initialise a lexer with a file
//
// @param source   the source
// @param name     the name for the source
// 
// @return the object
//

- (DLexer *) init :(id <DTextReadable>) source :(const char *) name
{
  [self init];
  
  [self source :source :name];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");

  return [super shallowCopy];
}


//// Deconstructor

//
// Free the lexer (but *NOT* the TextReadables)
//
// @return the object
//

- free
{
  [_sources free];

  [_text free];

  if (_source != nil)
    [_source free];

  [_whiteSpace free];
  [_expression free];
  
  return [super free];
}


//// Member methods

//
// Get the last scanned text
// 
// @return the last scanned text (or NULL)
// 

- (const char *) text
{
  return ([_text cstring]);
}

//
// Get the name of the current file
// 
// @return the name of the file in a new DText (or nil)
// 

- (const char *) name
{
  return ((_source != nil) ? [_source name] : NULL);
}
   
//
// Get the current line number
//
// @return the current line number
//

- (int) lineNumber
{
  return ((_source != nil) ? [_source lineNumber] : 0);
}

//
// Get the current column number
//
// @return the current column number
//

- (int) columnNumber
{
  return ((_source != nil) ? [_source columnNumber] : 0);
}

//
// Is the end of file reached ?
// 
// @return is it ?
// 

- (BOOL) isEof
{
  return _eoff;
}

//
// Set the skip white space expression
// 
// @param expression the skip white space expression (example : [[:space:]]*)
// 
// @return the object
// 

- (DLexer *) whiteSpace :(const char *) expression
{
  if (expression != NULL)
  {
    [_whiteSpace ccompile :expression];
  }
  
  return self;
}

//
// Set the case sensitivity of the lexer
// 
// @param caseSensitive  is the lexer case sensitive ?
// 
// @return the object
// 

- (DLexer *) caseSensitive :(BOOL) caseSensitive
{
  _caseSensitive = caseSensitive;
  
  return self;
}


//// Source methods

//
// Start using a source (an already open source is pushed on the sources stack)
// 
// @param source   the source
// @param name     the name for the source
// 
// @return success
// 

- (BOOL) source :(id <DTextReadable>) source :(const char *) name
{
  if (_source != nil)
  {
    [_sources append :source];
  }

  _source = [DSource new];
  
  return [_source set :source :name];
}

//
// Pop a source from the sources stack
// 
// @return is a source popped from the sources stack ?
// 

- (BOOL) popSource
{
  BOOL ok = NO;
  
  if ([_sources length] > 0)
  {
    if (_source != nil)
      [_source free];
    
    _source = [_sources pop];
    
    ok = YES;
  }
  
  return ok;
}


//// Scanner methods

// 
// Skip white space
// 
// @return is white space skipped ?
// 

- (BOOL) nextWhiteSpace
{
  BOOL scanned = [self checkWhiteSpace];
  
  if (scanned)
  {
    [self next];
  }

  return scanned;
}

//
// Scan the source for a constant string
// 
// @param cstring   the string to be scanned for
// 
// @return is it scanned ?
// 

- (BOOL) nextString :(const char *) cstring
{
  BOOL scanned = [self checkString :cstring];
  
  if (scanned)
  {
    [self next];
  }

  return scanned;
}

//
// Scan the source for a regular expression
//
// @param cstring    the regular expression to scan for
// 
// @return is the expression matched ?
// 

- (BOOL) nextExpression :(const char *) cstring
{
  BOOL scanned = [self checkExpression :cstring];
  
  if (scanned)
  {
    [self next];
  }
  
  return scanned;
}

//
// Scan the source till the end of line. Note: if the
// lexer is on the first column, there is no match, the line must
// be scanned before nextLine will match.
// 
// @return is it matched ?
// 

- (BOOL) nextLine
{
  _scanned = 0;
  
  [_text set :""];

  if (_source != nil)
  {
    if ([_source columnNumber] > 1)
    {
      _scanned = strlen([_source line]);
  
      if (_scanned > 0)
      {
        [_text set :[_source line] :0 :_scanned-1];
      
        [self next];
      
        return YES;
      }
    }
  }
  
  return NO;
}

//
// Check for white space
// 
// @return is white space found ?
//

- (BOOL) checkWhiteSpace
{
  _scanned = 0;
  
  [_text set :""];

  if (_source != nil)
  {
    _scanned = [_whiteSpace match :[_source line]];
    
    if (_scanned > 0)
    {
      [_text set :[_source line] :0 :_scanned-1];
    }
  }
  
  return (_scanned > 0);
}

//
// Check for source for a constant string
//
// @param cstring    the constant string to be checked for
// 
// @return is the constant string found in the source ?
// 

- (BOOL) checkString :(const char *) cstring
{
  _scanned = 0;
  
  [_text set :""];

  if (cstring != NULL)
  {
    if (_source != nil)
    {
      BOOL found  = NO;
      int  length = strlen(cstring);
      
      if (length > 0)
      {
        if (_caseSensitive)
        {
          found = (strncasecmp([_source line], cstring, length) == 0);
        }
        else
        {
          found = (strncmp([_source line], cstring, length) == 0);
        }
      
        if (found)
        {
          _scanned = length;
        
          [_text set :[_source line] :0 :length-1];
        }
      }
    }
  }
  else
    WARNING(DW_INVALID_ARG, "cstring");
  
  return (_scanned > 0);
}

//
// Check the source for a regular expression
// 
// @param cstring    the regular expression to be checked for
// 
// @return is the regular expression matched in the source ?
// 

- (BOOL) checkExpression :(const char *) cstring
{
  _scanned = 0;
 
  [_text set :""];
  
  if ((cstring != NULL) && (strlen(cstring) > 0))
  {
    if (_source != nil)
    {
      if (_caseSensitive)
      {
        [_expression ccompile :cstring];
      }
      else
      {
        [_expression icompile :cstring];
      }
      
      _scanned = [_expression match :[_source line]];
        
      if (_scanned > 0)
      {
        [_text set :[_source line] :0 :_scanned-1];
      }
    }
  }
  else
    WARNING(DW_INVALID_ARG, "cstring");

  return (_scanned > 0);
}
  
//
// Move the scanned indicator after a check.. method
// 
// @return the object
// 

- (DLexer *) next 
{
  if (_scanned > 0)
  {
    [self next :_scanned];
    
    _scanned = 0;
  }

  return self;
}

//
// Move the scanned indicator a number of positions
//
// @param positions  the number of position to move
//  
// @return the object
// 

- (DLexer *) next :(unsigned) positions
{
  if (_source != nil)
  {
    if ([_source scanned :positions])
    {
      do
      {
        if (![_source nextLine])
        {
          if (![self popSource])
          {
            _eoff = YES;
          }
        }
      }
      while ((!_eoff) && ([_source isLineScanned]));
    }
  }
  
  return self;
}
    

//// Error methods

//
// Generate an error on stderr for the current file and line
// 
// @param msg      the error message
// 
// @return the object
// 

- (DLexer *) error :(const char *) msg
{
  if (_source == nil)
  {
    WARNING(DW_OBJECT_NOT_INIT, "source");
  }
  else
  {
    [_source error :msg];
  }
  
  return self;
}
          
@end

#endif

/*==========================================================================*/
